package org.openlca.io.refdata; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.util.UUID; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamResult; import org.openlca.core.database.IDatabase; import org.openlca.core.database.LocationDao; import org.openlca.core.model.Location; import org.openlca.util.BinUtils; import org.openlca.util.KeyGen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.google.common.base.Strings; public class GeoKmzImport { private Logger log = LoggerFactory.getLogger(getClass()); private final File file; private final LocationDao dao; private XMLStreamReader reader; private Transformer transformer; public GeoKmzImport(File file, IDatabase db) { this.file = file; dao = new LocationDao(db); } public boolean run() { boolean foundDataInFile = false; try (FileInputStream is = new FileInputStream(file)) { reader = XMLInputFactory.newFactory() .createXMLStreamReader(is); transformer = TransformerFactory.newInstance() .newTransformer(); while (reader.hasNext()) { reader.next(); if (isStart(reader, "geography")) { foundDataInFile = handleGeography(); } } reader.close(); return foundDataInFile; } catch (Exception e) { log.error("failed to import KML data for geographies", e); return false; } } private boolean handleGeography() throws Exception { Loc loc = new Loc(); readLonLat(loc); while (reader.hasNext()) { reader.next(); if (isStart(reader, "name")) { String lang = reader.getAttributeValue(0); if (loc.name != null && !"en".equals(lang)) continue; loc.name = readText(); } else if (isStart(reader, "shortname")) { loc.shortName = readText(); } else if (isStart(reader, "kml")) loc.kmz = getKmz(reader); if (isEnd(reader, "geography")) break; } insertOrUpdate(loc); return loc.kmz != null; } private void readLonLat(Loc loc) { String longitude = null; String latitude = null; for (int i = 0; i < reader.getAttributeCount(); i++) { String attributeName = reader.getAttributeLocalName(i); if ("longitude".equals(attributeName)) longitude = reader.getAttributeValue(i); else if ("latitude".equals(attributeName)) latitude = reader.getAttributeValue(i); } try { if (longitude != null) loc.longitude = Double.parseDouble(longitude); if (latitude != null) loc.latitude = Double.parseDouble(latitude); } catch (Exception e) { log.error("Invalid latitude or longitude " + latitude + "," + longitude, e); } } private String readText() throws Exception { StringBuilder b = new StringBuilder(); reader.next(); while (reader.isCharacters()) { b.append(reader.getText()); reader.next(); } return b.toString(); } private void insertOrUpdate(Loc loc) { if (loc == null || !loc.valid()) return; log.trace("try insert KML for location {}", loc.name); try { String refId = KeyGen.get(loc.shortName); Location location = dao.getForRefId(refId); if (location == null) { insert(loc); } else if (loc.kmz != null) { update(location, loc.kmz); } } catch (Exception e) { log.error("failed to insert KML for location " + loc.shortName, e); } } private void insert(Loc loc) { Location location = new Location(); location.setName(loc.name); if (Strings.isNullOrEmpty(loc.shortName)) location.setRefId(UUID.randomUUID().toString()); else location.setRefId(KeyGen.get(loc.shortName)); location.setCode(loc.shortName); location.setLongitude(loc.longitude); location.setLatitude(loc.latitude); location.setKmz(loc.kmz); dao.insert(location); log.trace("New location added {}", loc.name); } private void update(Location location, byte[] kmz) { location.setKmz(kmz); dao.update(location); log.trace("KML in location {} updated", location.getName()); } private byte[] getKmz(XMLStreamReader reader) { try { DOMResult dom = new DOMResult(); transformer.transform(new StAXSource(reader), dom); Document doc = (Document) dom.getNode(); NodeList list = doc.getElementsByTagName("*"); String ns = "http://earth.google.com/kml/2.1"; for (int i = 0; i < list.getLength(); i++) { Node n = list.item(i); doc.renameNode(n, ns, n.getLocalName()); } ByteArrayOutputStream bout = new ByteArrayOutputStream(); transformer.transform(new DOMSource(doc), new StreamResult(bout)); byte[] bytes = bout.toByteArray(); return BinUtils.zip(bytes); } catch (Exception e) { log.error("failed to parse KML", e); return null; } } private boolean isStart(XMLStreamReader reader, String tagName) { return reader.isStartElement() && reader.getLocalName().equals(tagName); } private boolean isEnd(XMLStreamReader reader, String tagName) { return reader.isEndElement() && reader.getLocalName().equals(tagName); } /** Internal class for parsed data. */ private class Loc { String name; String shortName; double longitude; double latitude; byte[] kmz; boolean valid() { return name != null && shortName != null; } } }